package org.dslmagic.dsl;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.dslmagic.data.Group;
import org.dslmagic.data.Transformer;
import org.dslmagic.holder.ObjectHolder;
import org.dslmagic.interpreter.BaseDslInterpreter;
import org.dslmagic.interpreter.DslContext;
import org.dslmagic.interpreter.StandardDslContext;
import org.dslmagic.interpreter.StandardDslInterpreter;

public class Dsl {

	private String name;
	private Class<?> interpreterClass;
	private Object[] children;

	public static Dsl create(String name, Class<?> interpreterClass,
			Object... children) {
		return new Dsl(name, interpreterClass, children);
	}

	public static Dsl create(String name, Object... children) {
		return create(name, StandardDslInterpreter.class, children);
	}

	private Dsl(String name, Class<?> interpreterClass, Object... children) {
		this.name = name;
		this.interpreterClass = interpreterClass;
		this.children = children;
	}

	public void extract() {
		if (children.length == 1 && children[0] instanceof Object[]) {
			children = (Object[]) children[0];
		}
	}

	public Object kid(int i) {
		return children[i];
	}

	public String getName() {
		return getVisitedClass().getSimpleName();
	}

	public Class<?> getVisitedClass() {
		return getClass();
	}

	public Object visit(BaseDslInterpreter visitor) {
		try {
			Method method = visitor.getClass().getMethod(name,
					getVisitedClass());
			Object value = method.invoke(visitor, this);
			return value;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private List<Object> visitKids(BaseDslInterpreter visitor,
			Transformer transformer) {
		List<Object> values = new ArrayList<Object>();
		for (Object kid : children) {
			if (kid instanceof Dsl) {
				Dsl dsl = (Dsl) kid;
				values.add(transformer.transform(dsl.visit(visitor)));
			} else {
				values.add(transformer.transform(kid));
			}
		}
		return values;
	}

	private List<Object> transformKids(Transformer transformer) {
		List<Object> values = new ArrayList<Object>();
		for (Object kid : children) {
			values.add(transformer.transform(kid));
		}
		return values;
	}

	private Object visitKid(int i, BaseDslInterpreter visitor,
			Transformer transformer) {
		Object kid = kid(i);
		if (kid instanceof Dsl) {
			Dsl dsl = (Dsl) kid;
			return transformer.transform(dsl.visit(visitor));
		} else {
			return transformer.transform(kid);
		}
	}

	@Override
	public String toString() {
		String s = name;
		boolean first = true;
		for (Object kid : children) {
			s += (first ? "(" : ", ") + kid.toString();
			first = false;
		}
		return s + ")";
	}

	public Object obj(int i, StandardDslInterpreter interpreter) {
		return visitKid(i, interpreter, Transformer.TO_OBJ);
	}

	public String str(int i, StandardDslInterpreter interpreter) {
		return (String) visitKid(i, interpreter, Transformer.TO_STR);
	}

	public Integer num(int i, StandardDslInterpreter interpreter) {
		return (Integer) visitKid(i, interpreter, Transformer.TO_INT);
	}

	public Boolean bool(int i, StandardDslInterpreter interpreter) {
		return (Boolean) visitKid(i, interpreter, Transformer.TO_BOOL);
	}

	public Group group(int i, StandardDslInterpreter interpreter) {
		return (Group) visitKid(i, interpreter, Transformer.TO_GROUP);
	}

	public Dsl dsl(int i, StandardDslInterpreter interpreter) {
		return (Dsl) Transformer.TO_DSL.transform(kid(i));
	}

	public Object[] objs(StandardDslInterpreter interpreter) {
		List<Object> values = visitKids(interpreter, Transformer.TO_BOOL);
		return values.toArray(new Object[values.size()]);
	}

	public Boolean[] bools(StandardDslInterpreter interpreter) {
		List<Object> values = visitKids(interpreter, Transformer.TO_BOOL);
		return values.toArray(new Boolean[values.size()]);
	}

	public String[] strs(StandardDslInterpreter interpreter) {
		List<Object> values = visitKids(interpreter, Transformer.TO_STR);
		return values.toArray(new String[values.size()]);
	}

	public Integer[] nums(StandardDslInterpreter interpreter) {
		List<Object> values = visitKids(interpreter, Transformer.TO_INT);
		return values.toArray(new Integer[values.size()]);
	}

	public Group[] groups(StandardDslInterpreter interpreter) {
		List<Object> values = visitKids(interpreter, Transformer.TO_GROUP);
		return values.toArray(new Group[values.size()]);
	}

	public Dsl[] dsls(StandardDslInterpreter interpreter) {
		List<Object> values = transformKids(Transformer.TO_DSL);
		return values.toArray(new Dsl[values.size()]);
	}

	public static DslContext context() {
		return StandardDslContext.create();
	}

	public Object run(DslContext context) {
		try {
			BaseDslInterpreter interpreter = (BaseDslInterpreter) interpreterClass
					.newInstance();
			interpreter.setContext(context);
			return visit(interpreter);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public Object run(ObjectHolder<?> objectHolder) {
		return run(context().set("$", objectHolder.getObject()));
	}
	
	public Object run() {
		return run(context());
	}

}
